Skip to content

Static NAT + Masquerading / Static NAT + Port Forwarding#1548

Merged
qmonnet merged 24 commits into
mainfrom
pr/qmonnet/split-static-nat
Jun 9, 2026
Merged

Static NAT + Masquerading / Static NAT + Port Forwarding#1548
qmonnet merged 24 commits into
mainfrom
pr/qmonnet/split-static-nat

Conversation

@qmonnet

@qmonnet qmonnet commented May 20, 2026

Copy link
Copy Markdown
Member

The first commit are some preparatory clean-ups for the static NAT code:

  • refactor(nat): Move static NAT network function to dedicated file
  • refactor(nat): Move unicast check closer to source IP mapping lookup
  • refactor(nat): Handle zero-port check at port mapping lookup
  • feat(net): Add wrappers for transport with types (TCP/UDP)
  • refactor(nat): Use TcpUdp view in static NAT to simplify port update
  • refactor(net): Simplify metadata flag toggling

Then we add some finer control for running static NAT on a packet:

  • chore(flow-filter): Split requires_stateless_nat() into source/dest
  • feat(flow-filter): Tag packets for src/dst static NAT
  • feat(nat): Mark packets as NAT-ed for source or destination
  • feat(nat): For static NAT, only apply NAT for relevant direction(s)

We proceed to the changes we need to support combinations of NAT modes:

  • feat(dataplane): Move static NAT stage before port forwarding
  • feat(flow-filter,nat): Store static NAT requirements in flow info
  • feat(net): Embed flow key in packet metadata
  • feat(nat): Use initial IP addresses for forward flow table entry

We add support for ICMP Error messages with the NAT combinations (thanks Fredi!)

  • fix(nat): Remove NoMappingFound error
  • refactor(nat): Move NAT tables reader entering to process_packet()
  • feat(nat): Pass a NatTablesReaderFactory to the IcmpErrorHandler NF
  • feat(nat): Add static NAT support in IcmpErrorHandler

We update the validation step and add tests:

  • feat(config): Allow static + stateful NAT, on opposite ends
  • test(nat): Add tests for static NAT + {masquerade, port forwarding}

Please refer to individual commit description for details.

Summary

This PR refactors static NAT into a dedicated StatelessNat NF and extends NAT to support directional static NAT combined with masquerading and port forwarding, plus many related fixes, refactors, and tests.

Key Changes

  • Core refactor & new NF

    • Introduced StatelessNat network function (nat/src/stateless/nf.rs) with NatTablesReader/Writer and constructors (new/with_reader).
    • Moved static NAT logic into a dedicated module and adjusted module re-exports (nat/src/stateless/mod.rs).
  • Transport & header helpers

    • Added TCP/UDP transport wrappers and helpers (net/src/tcp_udp.rs) and exported tcp_udp module (net/src/lib.rs) to provide unified port access for TCP/UDP.
  • Packet metadata & directional NAT

    • Replaced the single stateless-NAT predicate with directional requirements: requires_static_nat_src / requires_static_nat_dst in PacketMeta and flow-filter logic.
    • Replaced single natted/req-stateless flags with per-direction flags: is_src_natted/is_dst_natted and set_static_nat_src/set_static_nat_dst; MetaFlags now include NATTED_SRC/NATTED_DST and REQ_STATIC_NAT_SRC/REQ_STATIC_NAT_DST.
    • PacketMeta now carries an optional flow_key: Option<Box> so stages can preserve the packet's initial flow key when creating forward flow-table entries.
  • Pipeline & flow-info changes

    • Inserted the static NAT stage before port-forwarding in the packet-processing pipeline (dataplane/src/packet_processor/mod.rs).
    • Added FlowInfoFlags bitflags and stored them in FlowInfo; FlowInfo::related_pair accepts flags to carry static-NAT requirements into flow state (net/src/flows/flow_info.rs).
    • Flow creation for masquerade and port-forwarding now distinguishes initial vs current flow keys and prefers packet.meta().flow_key when present (nat/src/stateful/nf.rs, nat/src/portfw/flow_state.rs).
  • Stateless NAT implementation & ICMP

    • StatelessNat performs directional source/destination static NAT using per-VNI NAT tables; supports address+port mappings and uses NonZero for translated ports; source mappings validate unicast IPs (nat/src/stateless/setup/tables.rs).
    • StatelessNat handles embedded inner headers inside ICMP error messages and maps translation failures to DoneReason values (nat/src/stateless/nf.rs).
    • ICMP error handling updated to set static-NAT metadata flags and refresh checksums where needed (nat/src/icmp_handler/nf.rs). Icmp error translation errors are mapped into DoneReason (nat/src/icmp_handler/icmp_error_msg.rs).
  • Port-forwarding & stateful changes

    • Port-forwarding flow-key construction is now fallible; build_portfw_flow_keys returns Result and do_port_forwarding handles failures without panics (nat/src/portfw/*).
    • PortFwTableWriter can be populated from a validated VPC table (update_from_vpc_table) (nat/src/portfw/portfwtable/access.rs).
    • Masquerade flow creation preserves initial/current key distinction and uses the initial key for allocation/flow binding (nat/src/stateful/nf.rs).
    • Packet NAT application sets src/dst natted flags when modifications occur; debug_assert! guards prevent double NAT application (nat/src/portfw/packet.rs, nat/src/stateful/packet.rs).
  • Configuration & tests

    • Relaxed overlay peering NAT compatibility validation to allow static NAT on one end with stateful NAT or port-forwarding on the other; updated validation logic and tests (config/src/external/overlay/vpc.rs and validation tests).
    • Updated tests and helpers across flow-filter and nat to use directional static-NAT metadata and the new FlowKey API.
    • Added extensive end-to-end tests (nat/src/test.rs) verifying:
      • Static NAT + masquerade (UDP, session establishment)
      • Static NAT + port forwarding (UDP, port mapping)
      • ICMP destination-unreachable handling for both combinations (outer and embedded header translations)
    • Adjusted long_dyn_pipeline test from 1000 to 999 stages to avoid stack exhaustion caused by increased packet/meta size (pipeline/src/pipeline.rs).
  • FlowKey / public-surface & other refactors

    • Refactored FlowKey from enum/wrapper (Uni/FlowKeyData) to a direct struct with FlowKey::new(...) and TryFrom<&Packet> implementations; removed FlowKeyData, Uni wrapper, and related public accessors (net/src/flows/flow_key.rs).
    • Updated many tests and modules to construct FlowKey via FlowKey::new or TryFrom<&Packet> (flow-entry, flow-filter, flow-entry tests, flow-table tests, stateful flows, etc.).
    • Display logic updated to render FlowKey fields directly (net/src/flows/display.rs).
    • Added FlowInfoFlags and exposed FlowInfo::get_flags; extended FlowInfo::related_pair signature to accept flags.
  • API & public-surface changes

    • Added public types/methods: FlowInfoFlags, FlowInfo::get_flags, extended FlowInfo::related_pair signature, PacketMeta.flow_key and per-direction APIs, TcpUdp types and trait impls, StatelessNat struct and nf exports.
    • Removed: ValidatedExpose::has_nat helper, PacketMeta requires_stateless_nat/set_stateless_nat accessors, and the previous NatTablesReader re-export from stateless mod.
  • Error mapping & minor utilities

    • Added From<&IcmpErrorMsgError> for DoneReason and From<&AllocatorError> for DoneReason to map internal errors to termination reasons.
    • Updated various modules to use the new TryTcpUdp helpers and flow-key APIs.
  • Performance / size note

    • Embedding Option in PacketMeta increased Packet and PacketMeta sizes (Packet from ~544 B → ~624 B; PacketMeta grew ~80 B). This caused stack pressure in a deep-pipeline test; the test was adjusted to 999 stages. The author intends to explore reducing metadata size.

Tests

  • Numerous unit and integration tests added/updated across nat, flow-filter, config, pipeline, and net to cover directional static NAT, combinations with masquerade and port-forwarding, and ICMP error translations.

@qmonnet qmonnet requested review from Fredi-raspall and mvachhar May 20, 2026 20:36
@qmonnet qmonnet self-assigned this May 20, 2026
@qmonnet qmonnet requested a review from a team as a code owner May 20, 2026 20:36
Copilot AI review requested due to automatic review settings May 20, 2026 20:36
@qmonnet qmonnet added the area/nat Related to Network Address Translation (NAT) label May 20, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the dataplane NAT pipeline to support combinations like static NAT + masquerading and static NAT + port forwarding by (a) making static NAT direction-aware, (b) persisting “initial” flow identity in packet metadata for later flow creation, and (c) reordering/propagating NAT requirements consistently through flow lookup/filtering and flow state.

Changes:

  • Split static NAT requirements into source vs destination (FlowFilter + PacketMeta flags) and track NAT application as src/dst NATed.
  • Persist an initial FlowKey on PacketMeta and propagate static-NAT requirement flags into FlowInfo so later packets rehydrate requirements from the flow table.
  • Refactor stateless NAT into a dedicated NF module, add TCP/UDP transport wrappers, and move static NAT stage earlier in the dataplane pipeline.

Reviewed changes

Copilot reviewed 37 out of 38 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
stats/src/dpstats.rs Updated access to src_vpcd via accessor after metadata encapsulation changes.
net/src/tcp_udp.rs Added TCP/UDP protocol-agnostic wrapper types (TcpUdp, TcpUdpMut).
net/src/packet/test_utils.rs Ensured test packet builders update embedded flow key after port changes.
net/src/packet/mod.rs Added Packet::update_flow_key() and initialize meta.flow_key on parse.
net/src/packet/meta.rs Split NAT flags into src/dst, privatized src_vpcd, added flow key + flow-flag computation helpers.
net/src/packet/display.rs Updated metadata display for new NAT flags + src_vpcd() accessor.
net/src/lib.rs Exported new tcp_udp module.
net/src/headers/mod.rs Added TryTcpUdp/TryTcpUdpMut traits and delegated implementations.
net/src/flows/flow_key.rs Updated flow-key derivation to use PacketMeta::src_vpcd() accessor.
net/src/flows/flow_info.rs Introduced FlowInfoFlags and extended related_pair() to store per-flow flags.
nat/src/test.rs Added end-to-end tests for static NAT + masquerade and static NAT + port forwarding.
nat/src/stateless/test.rs Updated tests to use new static NAT direction flags + set_src_vpcd.
nat/src/stateless/setup/tables.rs Tightened mapping lookups (unicast check; reject port 0) and updated types.
nat/src/stateless/setup/mod.rs Adjusted imports after stateless NAT refactor.
nat/src/stateless/nf.rs New stateless NAT NF implementation supporting directional static NAT + ICMP inner translation.
nat/src/stateless/mod.rs Refactored stateless NAT module to re-export new NF implementation.
nat/src/stateful/test.rs Updated tests for src_vpcd() accessor + set_src_vpcd.
nat/src/stateful/packet.rs Mark packets as src/dst NATed when stateful NAT modifies them.
nat/src/stateful/nf.rs Use initial vs current flow keys for session creation; store static-NAT requirement flags into flows.
nat/src/stateful/icmp_handling.rs Updated src_vpcd() accessor usage.
nat/src/portfw/test.rs Updated tests to use set_src_vpcd.
nat/src/portfw/portfwtable/access.rs Added helper to rebuild port-forwarding rules directly from validated VPC table.
nat/src/portfw/packet.rs Added debug asserts to prevent double NAT; (see comments re: metadata marking/message).
nat/src/portfw/nf.rs Updated src_vpcd() usage and threaded static-NAT flow flags into FlowInfo::related_pair.
nat/src/portfw/icmp_handling.rs Updated src_vpcd() accessor usage.
nat/src/portfw/flow_state.rs Build port-forwarding flow keys using stored initial flow key and current packet key.
nat/src/lib.rs Included new NAT test module.
nat/src/icmp_handler/nf.rs Updated src_vpcd() accessor usage.
flow-filter/src/tests.rs Updated tests to reference requires_static_nat() semantics and set_src_vpcd.
flow-filter/src/tables.rs Split “requires stateless nat” into src vs dst static-NAT requirements.
flow-filter/src/lib.rs Set directional static NAT flags on packets and rehydrate static-NAT requirements from FlowInfoFlags.
flow-filter/Cargo.toml Added bitflags dependency for flow info flags.
flow-entry/src/flow_table/nf_lookup.rs Updated flow creation helper calls for new related_pair() signature.
dataplane/src/packet_processor/mod.rs Reordered pipeline so static NAT runs before port forwarding.
dataplane/src/packet_processor/ipforward.rs Updated VNI annotation to use set_src_vpcd.
config/src/external/overlay/vpcpeering.rs Removed unused has_nat() helper.
config/src/external/overlay/vpc.rs Updated NAT-mode compatibility validation to allow static NAT alongside other modes on the opposite side.
Cargo.lock Added transitive lockfile updates for new dependency usage.
Comments suppressed due to low confidence (1)

nat/src/portfw/packet.rs:103

confidence: 8
tags: [logic]

`dnat_packet` can modify the packet's destination IP/port, but it never sets `PacketMeta`'s `dst_natted` flag when it does so. This makes `PacketMeta::is_dst_natted()` / display output inaccurate and weakens the intended "double NAT" protections. Set `dst_natted(true)` when `modified` is true.
debug_assert!(
    !packet.meta().is_dst_natted(),
    "Trying to apply double source NAT to packet!"
);
</details>

Comment thread nat/src/portfw/packet.rs Outdated
Comment thread nat/src/portfw/packet.rs
Comment thread nat/src/stateless/setup/tables.rs
@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch from e71a365 to 41463b9 Compare May 20, 2026 21:09
@qmonnet qmonnet mentioned this pull request May 20, 2026
@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch from 41463b9 to 1bee3c4 Compare May 20, 2026 22:25
@qmonnet

qmonnet commented May 20, 2026

Copy link
Copy Markdown
Member Author

Adding the flow key to the packet metadata increases the size of the metadata and hence the packet. With its 1,000 stages each adding a copy to the stack, long_dyn_pipeline() exhausts its stack. Here's the difference:

  ┌────────────────────┬────────┬────────┐
  │      size_of       │ before │ after  │
  ├────────────────────┼────────┼────────┤
  │ Packet<TestBuffer> │ 544 B  │ 624 B  │
  ├────────────────────┼────────┼────────┤
  │ PacketMeta         │ 72 B   │ 152 B  │
  ├────────────────────┼────────┼────────┤
  │ Option<FlowKey>    │ —      │ 80 B   │
  └────────────────────┴────────┴────────┘

I'll look into reducing the size of the metadata tomorrow.

@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch 2 times, most recently from 4bea4c4 to 1d0ea0e Compare May 21, 2026 17:00
@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch 5 times, most recently from 0bce3d4 to a36a1c4 Compare June 1, 2026 17:09

@daniel-noland daniel-noland left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other than some truly tiny nits this is ready to roll. I don't even think those nits need to be addressed in this PR, just noted for future. Nice work.

Comment thread config/src/external/overlay/tests.rs
Comment thread nat/src/icmp_handler/nf.rs Outdated
Comment thread nat/src/portfw/packet.rs
Comment thread nat/src/stateful/nf.rs
Comment thread net/src/packet/mod.rs Outdated
Comment thread nat/src/portfw/flow_state.rs
Comment thread nat/src/portfw/nf.rs
Comment thread nat/src/icmp_handler/nf.rs Outdated
@qmonnet

qmonnet commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

[This was a simple rebase, to get a clear diff when I push actual changes]

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR refactors NAT handling: introduces directional static NAT (src/dst) with a reader-backed StatelessNat NF, updates flow/packet flags and flow-key contracts, hardens stateful and port-forwarding flow-key logic, rewires the pipeline to run static NAT before port-forwarding, adjusts overlay validation/tests, and adds end-to-end static+masq/portfw/ICMP tests.

Changes

Static NAT and NAT pipeline behavior update

Layer / File(s) Summary
Overlay NAT validation rule changes
config/src/external/overlay/tests.rs, config/src/external/overlay/validation_tests.rs, config/src/external/overlay/vpc.rs, config/src/external/overlay/vpcpeering.rs
Peering NAT combination validation reworked to allow stateless+stateful and stateless+port-forwarding combinations previously rejected; tests and comments updated; ValidatedExpose::has_nat removed.
Flow flags and packet metadata contract updates
net/src/flows/flow_info.rs, net/src/packet/meta.rs, net/src/packet/display.rs, flow-entry/src/flow_table/nf_lookup.rs
Adds FlowInfoFlags with directional static-NAT bits; FlowInfo::related_pair accepts flags; PacketMeta holds optional flow_key, splits natted/requirement bits into src/dst, and updates flow flag computation and display.
TCP/UDP generic header accessor surface
net/src/lib.rs, net/src/tcp_udp.rs, net/src/headers/mod.rs
Adds tcp_udp module with TcpUdp/TcpUdpMut wrappers and TryTcpUdp traits; extends headers abstractions to expose protocol-agnostic port accessors and setters.
Flow-filter static NAT requirement derivation
flow-filter/Cargo.toml, flow-filter/src/lib.rs, flow-filter/src/tables.rs, flow-filter/src/tests.rs
Flow-filter sets static_nat_src/static_nat_dst from RemoteData and flow-info flags; introduces maybe_tag_with_flow_key to conditionally store meta.flow_key when static NAT mixes with port-forwarding/stateful NAT; updates NAT predicate helpers/tests.
Dedicated stateless NAT network function and table typing
nat/src/stateless/mod.rs, nat/src/stateless/nf.rs, nat/src/stateless/setup/mod.rs, nat/src/stateless/setup/tables.rs, nat/src/stateless/test.rs
Implements StatelessNat NF using per-VNI tables, ICMP inner-packet translation, and error mapping; PerVniTable returns Option<NonZero<u16>> for ports and UnicastIpAddr for source mappings; stateless tests updated to static NAT metadata.
Stateful and port-forwarding flow-key handling hardening
nat/src/stateful/nf.rs, nat/src/stateful/packet.rs, nat/src/portfw/flow_state.rs, nat/src/portfw/nf.rs, nat/src/portfw/packet.rs, nat/src/portfw/portfwtable/access.rs
Distinguishes initial vs current flow keys for masquerade allocation/lookups; port-forwarding key construction is fallible and handled gracefully; packet NAT mutation sets directional src_natted/dst_natted flags; adds PortFwTableWriter::update_from_vpc_table.
ICMP NAT path wiring and end-to-end pipeline coverage
nat/src/icmp_handler/nf.rs, nat/src/lib.rs, dataplane/src/packet_processor/mod.rs, nat/src/test.rs, pipeline/src/pipeline.rs
ICMP handler sets static NAT flags post-processing; dataplane pipeline replaces stateless_nat stage with static_nat (via StatelessNat::with_reader) before portfw; adds test module with integration tests for static+masq, static+portfw, and ICMP error translations; pipeline TTL test adjusted.

Sequence Diagram(s)

sequenceDiagram
  participant Ingress
  participant FlowFilter
  participant StaticNat
  participant PortForwarding
  participant StatefulMasquerade
  participant ICMPHandler
  participant Egress
  Ingress->>FlowFilter: determine dst_vpcd, set static_nat_src/dst flags, maybe tag flow_key
  FlowFilter->>StaticNat: packet with static NAT requirements
  StaticNat->>StaticNat: lookup per-VNI tables, translate src/dst, set src_natted/dst_natted
  StaticNat->>ICMPHandler: forward ICMP error packets for inner translation
  StaticNat->>PortForwarding: post-static-NAT packet
  PortForwarding->>PortForwarding: build flow-keys (fallible), apply port map
  PortForwarding->>StatefulMasquerade: packet for masquerade (if required)
  StatefulMasquerade->>Egress: final translated packet
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Suggested reviewers

  • mvachhar

Poem

🐰 A rabbit hops through NAT's new way,

Flow keys stored where headers play,
Static-src and static-dst in view,
ICMP and port maps now pass through,
Hooray for cleaner packet play!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main feature: combining static NAT with masquerading and port forwarding.
Docstring Coverage ✅ Passed Docstring coverage is 89.10% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr/qmonnet/split-static-nat

Warning

Review ran into problems

🔥 Problems

Stopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a @coderabbit review after the pipeline has finished.


Comment @coderabbitai help to get the list of available commands and usage tips.

@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch from 1169402 to 3cc328f Compare June 8, 2026 16:16
@qmonnet qmonnet requested a review from Fredi-raspall June 8, 2026 16:16
@qmonnet

qmonnet commented Jun 8, 2026

Copy link
Copy Markdown
Member Author

@Fredi-raspall Can you please look at the latest changes?

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
config/src/external/overlay/validation_tests.rs (1)

1214-1214: ⚡ Quick win

Rename tests to match the new expectation.

These tests now assert success, but function names still say _rejected, which makes failures and test reports misleading. Please rename them to _passes.

Suggested rename
-fn test_stateless_plus_stateful_rejected() {
+fn test_stateless_plus_stateful_passes() {
...
-fn test_stateless_plus_port_forwarding_rejected() {
+fn test_stateless_plus_port_forwarding_passes() {

Also applies to: 1245-1245

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@config/src/external/overlay/validation_tests.rs` at line 1214, Rename the
test functions whose names end with `_rejected` to `_passes` to reflect their
updated assertions: change `test_stateless_plus_stateful_rejected` to
`test_stateless_plus_stateful_passes` and the other similar function at the
second location (previously named with `_rejected`) to use `_passes`; update any
references and test attributes (e.g., `#[test]`) so the test names and failure
reports match the new "passes" expectation.
nat/src/stateless/test.rs (1)

605-610: ⚡ Quick win

These helpers never exercise the new per-direction NAT flags.

Both helpers unconditionally enable source and destination static NAT, so the suite can still pass if one of the direction-specific gates regresses. Please parameterize the flags here or add src-only/dst-only cases on top of these helpers.

Also applies to: 823-828

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nat/src/stateless/test.rs` around lines 605 - 610, The test helpers that call
build_test_ipv4_packet currently always enable both per-direction NAT flags via
packet.meta_mut().set_static_nat_src(true) and set_static_nat_dst(true), so they
never exercise src-only or dst-only behavior; update the helper(s) to accept
parameters (e.g., static_nat_src: bool, static_nat_dst: bool) and set those
flags based on the parameters, or add two new focused tests that call the helper
and set only packet.meta_mut().set_static_nat_src(true) /
set_static_nat_dst(false) and vice versa to cover src-only and dst-only paths;
ensure the code paths using packet.meta_mut(), build_test_ipv4_packet, and any
callers are updated to pass the new parameters or the new tests are added
alongside the existing ones.
nat/src/icmp_handler/nf.rs (1)

226-227: 💤 Low value

Consider clarifying why both src and dst static NAT flags are set.

Setting both set_static_nat_src(true) and set_static_nat_dst(true) unconditionally may be confusing to future maintainers. Based on the earlier review discussion, this is safe because the StatelessNat processor returns a no-op when no mapping exists for a direction. However, this invariant isn't obvious from the code.

Consider adding a brief comment explaining that both flags are set because the direction needing static NAT depends on the peering configuration, and the StatelessNat stage will only apply translations for directions with actual mappings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nat/src/icmp_handler/nf.rs` around lines 226 - 227, The code unconditionally
sets both packet.meta_mut().set_static_nat_src(true) and
packet.meta_mut().set_static_nat_dst(true), which is non-obvious to readers; add
a concise inline comment next to these calls explaining that both flags are set
because which direction requires static NAT depends on peering configuration and
the StatelessNat processor will perform a no-op for directions without a mapping
(so setting both is safe). Reference the packet.meta_mut() calls and the
StatelessNat processor in your comment so future maintainers understand the
invariant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@flow-filter/src/lib.rs`:
- Around line 353-370: The function maybe_tag_with_flow_key currently returns
immediately if packet.meta().flow_info.is_some(), which causes stale flow-info
to prevent capturing the pre-static-NAT FlowKey; change the logic so you do not
skip tagging for packets that will be invalidated: either call
should_invalidate_flow (or perform the invalidation check used by
process_packet) before deciding to skip, or invert the guard to only skip
tagging when an active/reusable flow remains (i.e., when
meta().flow_info.is_some() AND the flow is not slated for invalidation). Update
maybe_tag_with_flow_key (and the duplicate logic around lines ~471-482) to
consult should_invalidate_flow/process_packet’s invalidation predicate and only
return early when the existing flow is still valid/active; otherwise proceed to
compute FlowKey::try_from(net::flow_key::Uni(&*packet)) and set
packet.meta_mut().flow_key as before.

In `@flow-filter/src/tests.rs`:
- Around line 45-66: The current helper functions (needs_masquerade,
needs_static_nat, needs_port_forwarding, needs_no_nat) treat static NAT as
mutually exclusive with masquerade/port-forwarding and ignore the src/dst split;
update them to be orthogonal so tests can assert stateful NAT and
port-forwarding independently from source-vs-destination static NAT. Replace the
exclusory helpers with independent predicate helpers such as
is_stateful_nat(packet) (checks packet.meta().requires_stateful_nat()),
is_static_nat_src(packet) and is_static_nat_dst(packet) (use the new src/dst
static NAT flags on packet.meta()), and is_port_forwarding(packet) or
is_port_forwarding_src/dst as appropriate (check
packet.meta().requires_port_forwarding() per direction if available); remove
combinations that assume mutual exclusion (e.g., don't AND-not static-nat inside
is_stateful_nat or is_port_forwarding) so tests can combine these predicates to
express mixed static+masquerade/port-forwarding cases.

In `@nat/src/stateless/nf.rs`:
- Around line 254-257: destination_nat() is mutating embedded ICMP-error packets
without validating checksums; mirror source_nat() by invoking
validate_checksums_icmp() before calling
Self::translate_icmp_inner_packet_src_if_any so malformed ICMP errors are not
rewritten. Locate the block using Self::is_icmp_inner_translation_candidate and
add a checksum validation guard (same function used in source_nat) that returns
early on invalid checksums, then proceed to call
translate_icmp_inner_packet_src_if_any only when validate_checksums_icmp()
succeeds.

---

Nitpick comments:
In `@config/src/external/overlay/validation_tests.rs`:
- Line 1214: Rename the test functions whose names end with `_rejected` to
`_passes` to reflect their updated assertions: change
`test_stateless_plus_stateful_rejected` to `test_stateless_plus_stateful_passes`
and the other similar function at the second location (previously named with
`_rejected`) to use `_passes`; update any references and test attributes (e.g.,
`#[test]`) so the test names and failure reports match the new "passes"
expectation.

In `@nat/src/icmp_handler/nf.rs`:
- Around line 226-227: The code unconditionally sets both
packet.meta_mut().set_static_nat_src(true) and
packet.meta_mut().set_static_nat_dst(true), which is non-obvious to readers; add
a concise inline comment next to these calls explaining that both flags are set
because which direction requires static NAT depends on peering configuration and
the StatelessNat processor will perform a no-op for directions without a mapping
(so setting both is safe). Reference the packet.meta_mut() calls and the
StatelessNat processor in your comment so future maintainers understand the
invariant.

In `@nat/src/stateless/test.rs`:
- Around line 605-610: The test helpers that call build_test_ipv4_packet
currently always enable both per-direction NAT flags via
packet.meta_mut().set_static_nat_src(true) and set_static_nat_dst(true), so they
never exercise src-only or dst-only behavior; update the helper(s) to accept
parameters (e.g., static_nat_src: bool, static_nat_dst: bool) and set those
flags based on the parameters, or add two new focused tests that call the helper
and set only packet.meta_mut().set_static_nat_src(true) /
set_static_nat_dst(false) and vice versa to cover src-only and dst-only paths;
ensure the code paths using packet.meta_mut(), build_test_ipv4_packet, and any
callers are updated to pass the new parameters or the new tests are added
alongside the existing ones.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6d58f078-4895-4376-bbc0-22711b60e3b3

📥 Commits

Reviewing files that changed from the base of the PR and between dc35581 and 1169402.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (34)
  • config/src/external/overlay/tests.rs
  • config/src/external/overlay/validation_tests.rs
  • config/src/external/overlay/vpc.rs
  • config/src/external/overlay/vpcpeering.rs
  • dataplane/src/packet_processor/mod.rs
  • flow-entry/src/flow_table/nf_lookup.rs
  • flow-filter/Cargo.toml
  • flow-filter/src/lib.rs
  • flow-filter/src/tables.rs
  • flow-filter/src/tests.rs
  • nat/src/icmp_handler/nf.rs
  • nat/src/lib.rs
  • nat/src/portfw/flow_state.rs
  • nat/src/portfw/nf.rs
  • nat/src/portfw/packet.rs
  • nat/src/portfw/portfwtable/access.rs
  • nat/src/stateful/nf.rs
  • nat/src/stateful/packet.rs
  • nat/src/stateful/test.rs
  • nat/src/stateless/mod.rs
  • nat/src/stateless/natrw.rs
  • nat/src/stateless/nf.rs
  • nat/src/stateless/setup/mod.rs
  • nat/src/stateless/setup/tables.rs
  • nat/src/stateless/test.rs
  • nat/src/test.rs
  • net/src/flows/flow_info.rs
  • net/src/headers/mod.rs
  • net/src/lib.rs
  • net/src/packet/display.rs
  • net/src/packet/meta.rs
  • net/src/packet/mod.rs
  • net/src/tcp_udp.rs
  • pipeline/src/pipeline.rs
💤 Files with no reviewable changes (1)
  • config/src/external/overlay/vpcpeering.rs

Comment thread flow-filter/src/lib.rs Outdated
Comment thread flow-filter/src/tests.rs
Comment thread nat/src/stateless/nf.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nat/src/stateless/nf.rs`:
- Around line 191-213: The code mutates outer source IP/port in source_nat()
(translate_src/translate_src_port) before validate_checksums_icmp(), which can
break ICMPv6 checksum validation; move the ICMP error-path checks so
is_icmp_inner_translation_candidate(packet) and validate_checksums_icmp(packet)
(and the subsequent translate_icmp_inner_packet_dst_if_any call) are performed
before any outer-source mutation, or alternately perform checksum validation
against the original outer src by capturing it prior to
translate_src/translate_src_port; ensure modified is only updated after
successful translations and that translate_icmp_inner_packet_dst_if_any still
runs when validation passes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8634a997-9fe5-4e0f-b1cd-ece93367227d

📥 Commits

Reviewing files that changed from the base of the PR and between 1169402 and 3cc328f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (31)
  • config/src/external/overlay/tests.rs
  • config/src/external/overlay/validation_tests.rs
  • config/src/external/overlay/vpc.rs
  • config/src/external/overlay/vpcpeering.rs
  • dataplane/src/packet_processor/mod.rs
  • flow-entry/src/flow_table/nf_lookup.rs
  • flow-filter/Cargo.toml
  • flow-filter/src/lib.rs
  • flow-filter/src/tables.rs
  • flow-filter/src/tests.rs
  • nat/src/icmp_handler/nf.rs
  • nat/src/lib.rs
  • nat/src/portfw/flow_state.rs
  • nat/src/portfw/nf.rs
  • nat/src/portfw/packet.rs
  • nat/src/portfw/portfwtable/access.rs
  • nat/src/stateful/nf.rs
  • nat/src/stateful/packet.rs
  • nat/src/stateless/mod.rs
  • nat/src/stateless/nf.rs
  • nat/src/stateless/setup/mod.rs
  • nat/src/stateless/setup/tables.rs
  • nat/src/stateless/test.rs
  • nat/src/test.rs
  • net/src/flows/flow_info.rs
  • net/src/headers/mod.rs
  • net/src/lib.rs
  • net/src/packet/display.rs
  • net/src/packet/meta.rs
  • net/src/tcp_udp.rs
  • pipeline/src/pipeline.rs
💤 Files with no reviewable changes (1)
  • config/src/external/overlay/vpcpeering.rs
✅ Files skipped from review due to trivial changes (3)
  • nat/src/stateless/setup/mod.rs
  • net/src/lib.rs
  • config/src/external/overlay/tests.rs
🚧 Files skipped from review as they are similar to previous changes (22)
  • flow-entry/src/flow_table/nf_lookup.rs
  • pipeline/src/pipeline.rs
  • flow-filter/Cargo.toml
  • nat/src/lib.rs
  • nat/src/portfw/packet.rs
  • nat/src/stateful/packet.rs
  • net/src/tcp_udp.rs
  • nat/src/portfw/nf.rs
  • flow-filter/src/lib.rs
  • nat/src/portfw/flow_state.rs
  • nat/src/stateless/mod.rs
  • flow-filter/src/tests.rs
  • flow-filter/src/tables.rs
  • net/src/packet/display.rs
  • net/src/headers/mod.rs
  • config/src/external/overlay/validation_tests.rs
  • nat/src/stateless/test.rs
  • nat/src/stateless/setup/tables.rs
  • nat/src/test.rs
  • config/src/external/overlay/vpc.rs
  • nat/src/stateful/nf.rs
  • net/src/flows/flow_info.rs

Comment thread nat/src/stateless/nf.rs
qmonnet added 6 commits June 8, 2026 21:18
To handle some NAT mode combinations, we need to keep track of the
initial packet's addresses before the first NAT pass, when no flow
information was found for the packet. To do this, we optionally attach a
flow key to the packet's metadata.

We could attach the flow key unconditionally, but this would grow the
size of the metadata quite significantly (80 bytes estimated). Instead,
we Box<> it, and only allocate when it's relevant: when we have a
combination of static NAT with masquerading or port forwarding, and when
no flow table entry was found for the packet.

Note that adding even the boxed flow key makes the long_dyn_pipeline()
test fail with its 1,000 stages each adding a copy of the metadata to
the stack, resulting in stack overflow for the thread. Interestingly,
lowering down to 999 stages seems to be enough to make it pass again.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
So far, using NAT on both sides of a peering was supported only if both
sides used static NAT. Combining static NAT and masquerading or port
forwarding for a connection was not possible. We want to support it now,
and for this, we need to change the way we create flow table entries.

Here are some detailed explanations, and that the packet goes through.
Let's imagine two endpoints communicating through the gateway, and both
using NAT. For this connection, endpoint 1 has IP address "a", NAT-ed to
"A"; and endpoint 2 has IP address "b", exposed with NAT as "B". Here's
a simple diagram:

    +------------+            +---------+            +------------+
    |            | a        A |         | B        b |            |
    | Endpoint 1 |------------| Gateway |------------| Endpoint 2 |
    |            |            |         |            |            |
    +------------+            +---------+            +------------+

Let's look at a packet leaving endpoint 1 for endpoint 2:

  - Initially:          src: a, dst: B
  - After translation:  src: A, dst: b

Now the reply:

  - Initially:          src: b, dst: A
  - After translation:  src: B, dst: a

Let's consider a setup where endpoint 1 uses static NAT, and endpoint 2
uses port forwarding. When we process the packet in the port forwarding
stage, we want to create the following flow table entries:

  - Key: (src: a, dst: B), translation value: (src: A, dst: b) (forward)
  - Key: (src: b, dst: A), translation value: (src: B, dst: a) (reverse)

However, because the packet has already been through the static NAT
stage before reaching the port forwarding stage, we collect the
translated source IP address from the packet and create the following
entries:

  - Key: (src: A, dst: B), translation value: (src: A, dst: b) (forward)
  - Key: (src: b, dst: A), translation value: (src: B, dst: A) (reverse)

This is fine for the return path, but subsequent packets in the forward
direction will not match: the flow lookup stage comes before the static
NAT stage and "a" hasn't been translated to "A" yet before the flow
lookup. Flow lookup fails, we find no flow.

So instead, we want to create the forward-direction entry based not on
the address observed by the port forwarding stage, but on the initial
source IP address that the packet came with. In order to keep track of
that address, we associate a flow key for the packet's metadata. We can
use this flow key to create flow table entries for the forward
direction, but we keep using the addresses from the packet, after static
NAT has occurred, for the reverse direction.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
We've stopped using this error for the case where no mapping is found
for the source and destination IP addresses for the packet; we should
stop using it when no mapping is found for the addresses in the inner
packet for ICMP Error messages, too. We don't want to raise an error
when no mapping is found, it may be that no static NAT is required for
the address. At the moment this should not happen because we tag packets
that should be processed with static NAT, but it may happen in the
future (for ICMP Error message handling for some NAT modes combinations,
when packets haven't been through the flow-filter stage yet).

Signed-off-by: Quentin Monnet <qmo@qmon.net>
This is not completely ideal because we'd like to keep the possibility
to rework this code in the future to enter() just once for a batch of
packets, but this will help reuse the process_packet() function in the
ICMP Error handler in a follow-up commit.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
We've recently added support for static NAT in combination with
masquerade or with port forwarding, on the opposite ends of a peering.
But we haven't taken care of ICMP yet! In particular, the change means
that ICMP Error messages may require to go through static NAT after they
have been masqueraded or port-forwarded.

To do so, we simply allow ICMP Error packets to pass through the static
NAT stage even if they've been NAT-ed. We also need to make sure that
checksums are valid after any previous NAT processing of these packets.

Whether or not the packet actually requires NAT doesn't really matter
here. We don't have a good way to tell, because the packet hasn't been
through the flow-filter stage (we skipped it, given that the ICMP Error
handler set the dst_vpcd for the packet, because it might have an
unknown source IP but we want to allow it anyway), so we need to
translate as a best-effort. However, we do mark packets as source-NAT-ed
or dest-NAT-ed when we process them for masquerade or port forwarding,
and the static NAT stage does not re-apply translation for the indicated
address, so there's no risk to incur double translations.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
Now that we support using static NAT on one side of a peering and
masquerading or port forwarding on the other end, let's allow users to
deploy such configurations.

Also adjust relevant tests accordingly.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch from 3cc328f to a98c8c9 Compare June 8, 2026 20:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
nat/src/lib.rs (1)

33-33: 💤 Low value

Consider adding #[cfg(test)] to avoid compiling test code in non-test builds.

The mod test; declaration includes the test module unconditionally. Standard practice is to gate test modules with #[cfg(test)] so they are only compiled when running tests, reducing binary size and compile time in release builds.

♻️ Suggested change
+#[cfg(test)]
 mod test;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nat/src/lib.rs` at line 33, The test module is being compiled unconditionally
via the `mod test;` declaration; add a test-only cfg attribute so the `test`
module is only included in test builds by annotating the `mod test;` line with
`#[cfg(test)]` (i.e., gate the `test` module with the test configuration) to
prevent compiling tests into non-test/release builds and reduce build size/time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@nat/src/lib.rs`:
- Line 33: The test module is being compiled unconditionally via the `mod test;`
declaration; add a test-only cfg attribute so the `test` module is only included
in test builds by annotating the `mod test;` line with `#[cfg(test)]` (i.e.,
gate the `test` module with the test configuration) to prevent compiling tests
into non-test/release builds and reduce build size/time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 33666e8e-14ba-413f-bb53-e66e80388894

📥 Commits

Reviewing files that changed from the base of the PR and between 3cc328f and a98c8c9.

📒 Files selected for processing (15)
  • config/src/external/overlay/tests.rs
  • config/src/external/overlay/validation_tests.rs
  • config/src/external/overlay/vpc.rs
  • config/src/external/overlay/vpcpeering.rs
  • flow-filter/src/lib.rs
  • nat/src/icmp_handler/nf.rs
  • nat/src/lib.rs
  • nat/src/portfw/flow_state.rs
  • nat/src/portfw/nf.rs
  • nat/src/portfw/portfwtable/access.rs
  • nat/src/stateful/nf.rs
  • nat/src/stateless/nf.rs
  • nat/src/test.rs
  • net/src/packet/meta.rs
  • pipeline/src/pipeline.rs
💤 Files with no reviewable changes (1)
  • config/src/external/overlay/vpcpeering.rs
🚧 Files skipped from review as they are similar to previous changes (12)
  • pipeline/src/pipeline.rs
  • nat/src/portfw/portfwtable/access.rs
  • config/src/external/overlay/validation_tests.rs
  • config/src/external/overlay/tests.rs
  • nat/src/portfw/flow_state.rs
  • nat/src/icmp_handler/nf.rs
  • flow-filter/src/lib.rs
  • nat/src/test.rs
  • nat/src/stateless/nf.rs
  • config/src/external/overlay/vpc.rs
  • net/src/packet/meta.rs
  • nat/src/stateful/nf.rs

Add unit tests to validate the use in a pipeline of static NAT +
masquerade, or static NAT + port forwarding, on opposite sides of a
peering.

We set up a pipeline, create a packet, process it, and check for its
output; then we create a fake reply, process it, check as well; at last,
we re-process the original packet, to make sure that the connection
still works after the flow table entry has been created.

Signed-off-by: Quentin Monnet <qmo@qmon.net>
@qmonnet qmonnet force-pushed the pr/qmonnet/split-static-nat branch from a98c8c9 to 4813fb0 Compare June 8, 2026 20:29
@qmonnet

qmonnet commented Jun 9, 2026

Copy link
Copy Markdown
Member Author

Merging in #1550, which was independently approved, on top of this branch.

qmonnet and others added 5 commits June 9, 2026 16:01
Rather than the large translate_error() functions, implement From for
DoneReason.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Quentin Monnet <qmo@qmon.net>
FlowKey is an enum with a single Unidirectional variant wrapping
FlowKeyData, the second (bidirectional) variant was removed some time
ago. Turn FlowKey into a tuple struct so the wrapping no longer requires
pattern-matching, and drop the now-trivial match arms in PartialEq,
Hash, and the Display impl.

The inner field is left pub for now, a follow-up commit will merge
FlowKey and FlowKeyData into a single struct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Quentin Monnet <qmo@qmon.net>
FlowKey had become a tuple struct wrapping FlowKeyData, with accessors
data() and data_mut() used at every call site. The two types described
the same flow tuple, so we can fold FlowKeyData's fields, methods, and
Hash implementation directly into FlowKey and drop the indirection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Quentin Monnet <qmo@qmon.net>
FlowKey used to support a bidirectional variant alongside the
unidirectional one, and the builder was named uni() to disambiguate.
Bidirectional keys were dropped a while ago, and the remaining builder
named "uni()" is now confusing. Rename it to "new()" instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Quentin Monnet <qmo@qmon.net>
Uni<T> existed to tag a packet for unidirectional FlowKey extraction
back when bidirectional keys were also supported. Bidirectional keys are
gone, so Uni is not longer necessary, it simply adds dead weight that
every call site has to thread through, and makes the code less clear.

Convert the TryFrom impl to accept &Packet<Buf> directly, delete the
Uni struct, and drop the wrapper at the call sites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Quentin Monnet <qmo@qmon.net>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
net/src/flows/flow_key.rs (1)

578-585: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

FlowKey hash/eq contract is inconsistent for TCP/UDP port swaps.

FlowKey::hash is directional, but TcpProtoKey/UdpProtoKey equality is symmetric. For same-IP flows, reversed ports can compare equal while producing different hashes, which can break hash-map lookups/removals.

🛠️ Proposed fix
 impl PartialEq for TcpProtoKey {
     fn eq(&self, other: &Self) -> bool {
-        self.symmetric_eq(other)
+        self.src_port == other.src_port && self.dst_port == other.dst_port
     }
 }

 impl PartialEq for UdpProtoKey {
     fn eq(&self, other: &Self) -> bool {
-        self.symmetric_eq(other)
+        self.src_port == other.src_port && self.dst_port == other.dst_port
     }
 }

As per coding guidelines, "Find logic errors in the code under review. If confident that code is incorrect, suggest a fix."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@net/src/flows/flow_key.rs` around lines 578 - 585, The FlowKey Hash
implementation is directional while TcpProtoKey/UdpProtoKey equality treats port
pairs symmetrically, so implement a canonical (order-independent) hashing for
the protocol-port part: in the Hash for FlowKey (the impl Hash for FlowKey
function) replace the directional calls to proto_key_info.hash_src(state) and
proto_key_info.hash_dst(state) with a single canonical hashing step that orders
the (src_ip, src_port) vs (dst_ip, dst_port) pair deterministically (e.g.,
compare src_ip/dst_ip and for equal IPs compare ports or simply use min/max for
ports) and then hash the protocol + ordered port pair; this keeps FlowKey::hash
consistent with TcpProtoKey/UdpProtoKey equality and fixes map lookup/removal
mismatches.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nat/src/icmp_handler/icmp_error_msg.rs`:
- Around line 50-52: The match arm mapping
IcmpErrorMsgError::NoTranslationPossible to DoneReason::InternalFailure is
incorrect; change that arm to return the semantic DoneReason for expected
non-translatable packets (e.g. DoneReason::NoTranslationPossible or
DoneReason::NoTranslation if that’s the existing enum variant) instead of
InternalFailure, update the match to use the correct DoneReason variant in the
function handling IcmpErrorMsgError, and adjust/add tests to assert the new
mapping for NoTranslationPossible.

---

Outside diff comments:
In `@net/src/flows/flow_key.rs`:
- Around line 578-585: The FlowKey Hash implementation is directional while
TcpProtoKey/UdpProtoKey equality treats port pairs symmetrically, so implement a
canonical (order-independent) hashing for the protocol-port part: in the Hash
for FlowKey (the impl Hash for FlowKey function) replace the directional calls
to proto_key_info.hash_src(state) and proto_key_info.hash_dst(state) with a
single canonical hashing step that orders the (src_ip, src_port) vs (dst_ip,
dst_port) pair deterministically (e.g., compare src_ip/dst_ip and for equal IPs
compare ports or simply use min/max for ports) and then hash the protocol +
ordered port pair; this keeps FlowKey::hash consistent with
TcpProtoKey/UdpProtoKey equality and fixes map lookup/removal mismatches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b951645-ee91-447b-b527-f1f53408b6c5

📥 Commits

Reviewing files that changed from the base of the PR and between 4813fb0 and 1dd7e31.

📒 Files selected for processing (14)
  • flow-entry/src/flow_table/nf_lookup.rs
  • flow-entry/src/flow_table/table.rs
  • flow-filter/src/lib.rs
  • flow-filter/src/tests.rs
  • nat/src/icmp_handler/icmp_error_msg.rs
  • nat/src/portfw/flow_state.rs
  • nat/src/stateful/allocation.rs
  • nat/src/stateful/flows.rs
  • nat/src/stateful/nf.rs
  • nat/src/stateful/test.rs
  • nat/src/stateless/nf.rs
  • net/src/flows/display.rs
  • net/src/flows/flow_key.rs
  • net/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (5)
  • nat/src/portfw/flow_state.rs
  • flow-filter/src/tests.rs
  • flow-filter/src/lib.rs
  • nat/src/stateless/nf.rs
  • nat/src/stateful/nf.rs

Comment thread nat/src/icmp_handler/icmp_error_msg.rs
@qmonnet qmonnet added this pull request to the merge queue Jun 9, 2026
Merged via the queue into main with commit 58da74b Jun 9, 2026
35 checks passed
@qmonnet qmonnet deleted the pr/qmonnet/split-static-nat branch June 9, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/nat Related to Network Address Translation (NAT)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Split stateless NAT stage into two NAT: Investigate stateless + stateful NAT

5 participants